package dk.aiszone.lang.autosource;

import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.TypeParameter;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.InitializerDeclaration;
import japa.parser.ast.body.JavadocComment;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.expr.MemberValuePair;
import japa.parser.ast.expr.NormalAnnotationExpr;
import japa.parser.ast.expr.StringLiteralExpr;
import japa.parser.ast.stmt.BlockStmt;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.type.ReferenceType;
import japa.parser.ast.type.Type;
import japa.parser.ast.type.VoidType;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import dk.aiszone.lang.autosource.generatorobjects.AbstractBody;
import dk.aiszone.lang.autosource.generatorobjects.Annotation;
import dk.aiszone.lang.autosource.generatorobjects.Argument;
import dk.aiszone.lang.autosource.generatorobjects.Class;
import dk.aiszone.lang.autosource.generatorobjects.ClassBody;
import dk.aiszone.lang.autosource.generatorobjects.Declaration;
import dk.aiszone.lang.autosource.generatorobjects.GenericClass;
import dk.aiszone.lang.autosource.generatorobjects.Import;
import dk.aiszone.lang.autosource.generatorobjects.InterfaceBody;
import dk.aiszone.lang.autosource.generatorobjects.JavaFile;
import dk.aiszone.lang.autosource.generatorobjects.Method;
import dk.aiszone.lang.autosource.generatorobjects.MethodDeclaration;
import dk.aiszone.lang.autosource.generatorobjects.Scope;
import dk.aiszone.lang.autosource.generatorobjects.StaticArea;
import dk.aiszone.lang.autosource.generatorobjects.Variable;
import dk.aiszone.lang.autosource.generatorobjects.GenericClass.Operator;
import dk.aiszone.lang.autosource.generatorobjects.interfaces.BodyContainer;
import dk.aiszone.lang.autosource.generatorobjects.interfaces.DeclarableObject;

/**
 * The <code>SourceFileParser</code> is used for parsing a .java file<br>
 * into an object model containing the objects found the <code>autosource</code> package.
 * 
 * @author Ais
 */
public class SourceFileParser {

    private JavaFile javaFile;

    /**
     * Constructor for the <code>SourceFileParser</code> class.
     * 
     * @param file
     *            The location of the file to parse.
     * @throws ParseException
     *             Thrown if the file is malformed.
     */
    public SourceFileParser(String file) throws ParseException {
        CompilationUnit compilationUnit = JavaParser.parse(new File(file));

        analyzeResult(compilationUnit);
    }

    /**
     * Retrieve the result of the parser.
     * 
     * @return The <code>JavaFile</code> representation of the parsed source.
     */
    public JavaFile getResultingJavaFile() {
        return javaFile;
    }

    /**
     * Converts the supplied <code>Type</code> to a <code>Class</code>.
     * 
     * @param type
     *            The <code>Type</code> to convert
     * @return The resulting <code>Class</code>.
     */
    protected Class createClass(Type type) {
        if (type instanceof VoidType) {
            return Class.VOID;
        }

        if (type instanceof PrimitiveType) {
            PrimitiveType primitive = (PrimitiveType)type;
            return new Class(primitive.toString());
        }

        if (type instanceof ReferenceType) {
            return createClass(((ReferenceType)type).getType());
        }

        ClassOrInterfaceType classType = (ClassOrInterfaceType)type;
        Class _class = new Class(classType.getName());

        if (classType.getTypeArgs() != null) {
            for (Iterator<Type> iterator = classType.getTypeArgs().iterator(); iterator.hasNext();) {
                _class.addGeneric(createGenericClass(iterator.next()));
            }
        }

        return _class;
    }

    /**
     * Creates a <code>Declaration</code> object from the supplied modifiers.
     * 
     * @param modifiers
     *            <code>int</code> representation of modifiers.
     * @return The corresponding <code>Declaration</code> object.
     */
    protected Declaration createDeclaration(int modifiers) {
        Declaration declaration = new Declaration();
        declaration.setAbstract(ModifierSet.isAbstract(modifiers));
        declaration.setFinal(ModifierSet.isFinal(modifiers));
        declaration.setStatic(ModifierSet.isStatic(modifiers));

        if (ModifierSet.isPublic(modifiers)) {
            declaration.setScope(Scope.PUBLIC);
        } else if (ModifierSet.isProtected(modifiers)) {
            declaration.setScope(Scope.PROTECTED);
        } else if (ModifierSet.isPrivate(modifiers)) {
            declaration.setScope(Scope.PRIVATE);
        } else {
            declaration.setScope(Scope.DEFAULT);
        }

        return declaration;
    }

    /**
     * Creates a <code>Method</code> based on the supplied <code>japa.parser.ast.body.MethodDeclaration</code>.
     * 
     * @param md
     *            The <code>japa.parser.ast.body.MethodDeclaration</code> to convert.
     * @return The resulting <code>Method</code>.
     */
    protected Method createMethod(japa.parser.ast.body.MethodDeclaration md) {
        Method method = new Method();
        handleMethodDeclaration(method, md);

        handleBody(md.getBody(), method);
        return method;
    }

    /**
     * Handle the conversion of a <code>BlockStmt</code> into the contents of a <code>BodyContainer</code>.
     * 
     * @param block
     *            The <code>BlockStmt</code> to parse
     * @param bodycontainer
     *            The <code>BodyContainer</code> to add the contents to.
     */
    protected void handleBody(BlockStmt block, BodyContainer bodycontainer) {
        String body = block.toString();
        body = body.substring(1);
        body = body.substring(0, body.length() - 1);
        body = body.trim();

        String[] lines = body.split("\n");
        for (int i = 0; i < lines.length - 1; i++) {
            String line = lines[i].trim();
            bodycontainer.appendToBody(line).appendToBody("\n");
        }
        bodycontainer.appendToBody(lines[lines.length - 1].trim());
    }

    /**
     * Handle the conversion between a <code>japa.parser.ast.body.MethodDeclaration</code> and a <code>MethodDeclaration</code>.
     * 
     * @param methodDeclaration
     *            The <code>MethodDeclaration</code> to set the attributes on.
     * @param md
     *            The <code>japa.parser.ast.body.MethodDeclaration</code> to retrieve the attributes from.
     */
    protected void handleMethodDeclaration(MethodDeclaration methodDeclaration, japa.parser.ast.body.MethodDeclaration md) {
        methodDeclaration.setName(md.getName());
        methodDeclaration.setDeclaration(createDeclaration(md.getModifiers()));
        methodDeclaration.setReturnType(createClass(md.getType()));

        if (md.getParameters() != null) {
            for (Iterator<Parameter> iterator = md.getParameters().iterator(); iterator.hasNext();) {
                Parameter parameter = iterator.next();
                methodDeclaration.addArgument(new Argument(createClass(parameter.getType()), parameter.getId().toString()));
            }
        }
        handleAnnotations(methodDeclaration, md.getAnnotations());

    }

    private void analyzeResult(CompilationUnit cu) {
        ClassOrInterfaceDeclaration declaration = (ClassOrInterfaceDeclaration)cu.getTypes().get(0);

        AbstractBody<? extends MethodDeclaration> body = (declaration.isInterface() ? new InterfaceBody() : new ClassBody());
        body.setDeclaration(createDeclaration(declaration.getModifiers()));
        body.setName(declaration.getName());

        // Handle imports
        for (Iterator<ImportDeclaration> iterator = cu.getImports().iterator(); iterator.hasNext();) {
            ImportDeclaration id = iterator.next();

            body.addNeededImport(new Import(id.getName().toString()));
        }

        // Handle type parameters
        if (declaration.getTypeParameters() != null) {
            for (Iterator<TypeParameter> iterator = declaration.getTypeParameters().iterator(); iterator.hasNext();) {
                TypeParameter type = iterator.next();
                body.addGenerics(createGenericClass(type));
            }
        }

        // Handle annotations
        handleAnnotations(body, declaration.getAnnotations());
        handleJavaDoc(body, declaration.getJavaDoc());

        if (declaration.getExtends() != null) {
            for (Iterator<ClassOrInterfaceType> iterator = declaration.getExtends().iterator(); iterator.hasNext();) {
                ClassOrInterfaceType cit = iterator.next();

                if (body instanceof InterfaceBody) {
                    ((InterfaceBody)body).addExtends(createClass(cit));
                } else {
                    ((ClassBody)body).setExtendsClass(createClass(cit));
                    break;
                }
            }
        }

        if (declaration.getImplements() != null) {
            for (Iterator<ClassOrInterfaceType> iterator = declaration.getImplements().iterator(); iterator.hasNext();) {
                ClassOrInterfaceType cit = iterator.next();

                ((ClassBody)body).addImplements(createClass(cit));
            }
        }

        if (declaration.getMembers() != null) {
            for (Iterator<BodyDeclaration> iterator = declaration.getMembers().iterator(); iterator.hasNext();) {
                BodyDeclaration bd = iterator.next();

                if (bd instanceof FieldDeclaration) {
                    List<Variable> variables = createVariables((FieldDeclaration)bd);
                    for (Iterator<Variable> resultIter = variables.iterator(); resultIter.hasNext();) {
                        ((ClassBody)body).addVariable(resultIter.next());
                    }
                } else if (bd instanceof InitializerDeclaration) {
                    ((ClassBody)body).addStaticArea(createStaticArea((InitializerDeclaration)bd));
                } else if (bd instanceof japa.parser.ast.body.MethodDeclaration) {
                    if (body instanceof InterfaceBody) {
                        ((InterfaceBody)body).addMethod(createMethodDeclaration((japa.parser.ast.body.MethodDeclaration)bd));
                    } else {
                        ((ClassBody)body).addMethod(createMethod((japa.parser.ast.body.MethodDeclaration)bd));
                    }
                } else {
                    throw new RuntimeException("Unknown type: " + bd);
                }

            }
        }

        javaFile = new JavaFile(cu.getPakage().toString(), body);
    }

    private void handleJavaDoc(AbstractBody<? extends MethodDeclaration> body, JavadocComment javaDoc) {

    }

    private Annotation createAnnotation(AnnotationExpr ae) {
        if (ae instanceof NormalAnnotationExpr) {
            NormalAnnotationExpr nae = (NormalAnnotationExpr)ae;

            Annotation annotation = new Annotation(new Class(nae.getName().getName()));
            if (nae.getPairs() != null) {
                for (Iterator<MemberValuePair> pairIterator = nae.getPairs().iterator(); pairIterator.hasNext();) {
                    MemberValuePair member = pairIterator.next();

                    String name = member.getName();
                    if (member.getValue() instanceof StringLiteralExpr) {
                        annotation.addStringValue(name, ((StringLiteralExpr)member.getValue()).getValue());
                    } else if (member.getValue() instanceof NormalAnnotationExpr) {
                        annotation.addValue(name, createAnnotation((AnnotationExpr)member.getValue()));
                    }
                }
            }
            return annotation;
        }
        throw new RuntimeException("Unknown type: " + ae);
    }

    private GenericClass createGenericClass(Type type) {
        return new GenericClass(createClass(type));
    }

    private GenericClass createGenericClass(TypeParameter type) {
        if (type.getTypeBound() != null) {
            return new GenericClass(type.getName(), Operator.EXTENDS, createClass(type.getTypeBound().get(0)));
        }

        return new GenericClass(new Class(type.getName()));
    }

    private MethodDeclaration createMethodDeclaration(japa.parser.ast.body.MethodDeclaration md) {
        MethodDeclaration methodDeclaration = new MethodDeclaration();
        handleMethodDeclaration(methodDeclaration, md);
        return methodDeclaration;
    }

    private StaticArea createStaticArea(InitializerDeclaration id) {
        StaticArea staticArea = new StaticArea();

        handleBody(id.getBlock(), staticArea);

        return staticArea;
    }

    private List<Variable> createVariables(FieldDeclaration fd) {
        List<Variable> variables = new ArrayList<Variable>();

        for (Iterator<VariableDeclarator> iterator = fd.getVariables().iterator(); iterator.hasNext();) {
            VariableDeclarator vd = iterator.next();

            Variable variable = new Variable();
            variable.setDeclaration(createDeclaration(fd.getModifiers()));
            variable.setName(vd.getId().getName());
            variable.setType(createClass(fd.getType()));
            if (vd.getInit() != null) {
                variable.setInitialization(vd.getInit().toString());
            }

            variables.add(variable);
        }
        return variables;
    }

    private void handleAnnotations(DeclarableObject declObject, List<AnnotationExpr> annotations) {
        if (annotations != null) {
            for (Iterator<AnnotationExpr> iterator = annotations.iterator(); iterator.hasNext();) {
                Annotation annotation = createAnnotation(iterator.next());

                declObject.addAnnotation(annotation);
            }
        }
    }
}